/*
 * Copyright 2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.pajamas.starter.integration.module.context;

import com.google.common.collect.Maps;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.pajamas.daydream.treeFlatFormat.json.AbstractJsonTreeFlatFormatter;
import org.pajamas.daydream.treeFlatFormat.json.JacksonJsonTreeFlatFormatter;
import org.pajamas.daydream.wrapper.commons.jackson.CommonsWrapperModule;
import org.pajamas.starter.core.util.ClasspathUtils;
import org.pajamas.starter.core.util.SequenceUtils;
import org.pajamas.starter.core.util.StreamOptUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;

import lombok.extern.slf4j.Slf4j;

/**
 * @author william
 * @since 2024/1/6
 */
@Slf4j
public class PajamasEnvPostProcessor implements EnvironmentPostProcessor {
    private static final ObjectMapper OBJECT_MAPPER;
    private static final AbstractJsonTreeFlatFormatter JSON_TREE_FLAT_FORMATTER;
    private static final String FILE_NAME_DEFAULT_CONF;

    private static final String JSON_DIR_PATH;
    private static final String PROP_SRC_NAME;

    static {
        OBJECT_MAPPER = new ObjectMapper()
                .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
                .registerModule(new Jdk8Module())
                .registerModule(new CommonsWrapperModule());
        JSON_TREE_FLAT_FORMATTER = new JacksonJsonTreeFlatFormatter(OBJECT_MAPPER);
        FILE_NAME_DEFAULT_CONF = "default.json";
        JSON_DIR_PATH = "/starter/";
        PROP_SRC_NAME = "pajamas.starter.json";
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> createPropSrcIfAbsent(ConfigurableEnvironment environment,
                                                      Class<?> bootClass) {
        PropertySource<?> propertySource = environment.getPropertySources().get(PROP_SRC_NAME);
        if (propertySource == null) {
            String basePackage = ClassUtils.getPackageName(bootClass);
            Map<String, Object> presetMap = Maps.newConcurrentMap();
            presetMap.put(PajamasPropSrcConstants.BOOT_CLASS_NAME, bootClass.getName());
            presetMap.put(PajamasPropSrcConstants.BASE_PACKAGE_APP, basePackage);
            // TODO upgrade the latest discovery client lib to obtain current instance-id
            // https://github.com/alibaba/nacos/issues/10223
            // temporary solution: generate an id for Spring Boot Apps via UUID (no business with Spring Cloud Apps)
            presetMap.put(PajamasPropSrcConstants.INSTANCE_ID, SequenceUtils.uuid());
            presetMap.put(PajamasPropSrcConstants.BASE_PACKAGE_REPO, basePackage);
            propertySource = new MapPropertySource(PROP_SRC_NAME, presetMap);
            environment.getPropertySources().addLast(propertySource);
        }
        return ((PropertySource<Map<String, Object>>) propertySource).getSource();
    }

    /**
     * fix #I96S30: starter.default.json must be loaded fst
     */
    private static boolean isDefaultConf(Resource resource) {
        String fileName = null;
        try {
            fileName = resource.getURL().getFile();
        } catch (IOException ex) {
            throw new PajamasContextEnvException(ex);
        }
        String shortenFileName = FilenameUtils.getName(fileName);
        return StringUtils.equals(FILE_NAME_DEFAULT_CONF, shortenFileName);
    }

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        // read json files
        Resource[] resources = new Resource[0];
        try {
            resources = ClasspathUtils.scanResources(JSON_DIR_PATH, "*.json", true);
        } catch (IOException ex) {
            throw new PajamasContextEnvException(ex);
        }

        // parse to flat map
        final Map<String, Object> flatMap = Maps.newHashMap();
        Arrays.stream(resources)
                .filter(Objects::nonNull)
                .sorted(StreamOptUtils.Sorting.byPredicate(PajamasEnvPostProcessor::isDefaultConf))
                .map(JSON_TREE_FLAT_FORMATTER::format)
                .filter(MapUtils::isNotEmpty)
                .forEach(flatMap::putAll);
        if (MapUtils.isEmpty(flatMap)) {
            return;
        }

        // merge the property source
        Map<String, Object> source = this.createPropSrcIfAbsent(environment, application.getMainApplicationClass());
        source.putAll(flatMap);
    }
}
